﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using NewLife;
using NewLife.Caching;

namespace Stardust.Models
{
    /// <summary>进程信息</summary>
    public class AppInfo
    {
        #region 属性
        /// <summary>进程标识</summary>
        public Int32 Id { get; set; }

        /// <summary>进程名称</summary>
        public String Name { get; set; }

        /// <summary>版本</summary>
        public String Version { get; set; }

        /// <summary>用户名</summary>
        public String UserName { get; set; }

        /// <summary>机器名</summary>
        public String MachineName { get; set; }

        /// <summary>开始时间</summary>
        public DateTime StartTime { get; set; }

        /// <summary>处理器时间。单位ms</summary>
        public Int32 ProcessorTime { get; set; }

        /// <summary>物理内存</summary>
        public Int64 WorkingSet { get; set; }

        /// <summary>线程数</summary>
        public Int32 Threads { get; set; }

        /// <summary>句柄数</summary>
        public Int32 Handles { get; set; }

        /// <summary>连接数</summary>
        public Int32 Connections { get; set; }

        private readonly Process _process;
        #endregion

        #region 构造
        /// <summary>实例化进程信息</summary>
        public AppInfo() { }

        /// <summary>根据进程对象实例化进程信息</summary>
        /// <param name="process"></param>
        public AppInfo(Process process)
        {
            try
            {
                Id = process.Id;
                Name = process.ProcessName;
                if (Name == "dotnet" || "*/dotnet".IsMatch(Name)) Name = GetProcessName(process);
                //StartTime = process.StartTime;
                //ProcessorTime = (Int64)process.TotalProcessorTime.TotalMilliseconds;

                _process = process;

                Refresh();
            }
            catch (Win32Exception) { }
            catch (InvalidOperationException) { }
        }
        #endregion

        #region 方法
        /// <summary>刷新进程相关信息</summary>
        public void Refresh()
        {
            try
            {
                _process.Refresh();

                WorkingSet = _process.WorkingSet64;
                Threads = _process.Threads.Count;
                Handles = _process.HandleCount;

                UserName = Environment.UserName;
                MachineName = Environment.MachineName;
                StartTime = _process.StartTime;
                ProcessorTime = (Int32)_process.TotalProcessorTime.TotalMilliseconds;

                try
                {
                    // 调用WindowApi获取进程的连接数
                    var tcps = NetHelper.GetAllTcpConnections();
                    if (tcps != null && tcps.Length > 0)
                    {
                        var pid = Process.GetCurrentProcess().Id;
                        Connections = tcps.Count(e => e.ProcessId == pid);
                    }
                }
                catch { }
            }
            catch (Win32Exception) { }
        }

        private static ICache _cache = new MemoryCache();
        /// <summary>获取进程名</summary>
        /// <param name="process"></param>
        /// <returns></returns>
        public static String GetProcessName(Process process)
        {
            if (Runtime.Linux)
            {
                try
                {
                    var lines = File.ReadAllText($"/proc/{process.Id}/cmdline").Trim('\0', ' ').Split('\0');
                    if (lines.Length > 1) return lines[1];
                }
                catch { }
            }
            else if (Runtime.Windows)
            {
                // 缓存，避免频繁执行
                var key = process.Id + "";
                if (_cache.TryGetValue<String>(key, out var value)) return value;

                try
                {
                    var dic = ReadWmic("process", "processId=" + process.Id, "commandline");
                    if (dic.TryGetValue("commandline", out var str))
                    {
                        //XTrace.WriteLine(str);
                        var p = str.IndexOf('\"');
                        if (p >= 0)
                        {
                            var p2 = str.IndexOf('\"', p + 1);
                            if (p2 > 0) str = str.Substring(p2 + 1);
                        }
                        //XTrace.WriteLine(str);
                        var ss = str.Split(' ');
                        if (ss.Length >= 2)
                        {
                            _cache.Set(key, ss[1], 600);
                            return ss[1];
                        }
                    }
                }
                catch { }

                _cache.Set(key, process.ProcessName, 600);
            }

            return process.ProcessName;
        }

        /// <summary>通过WMIC命令读取信息</summary>
        /// <param name="type"></param>
        /// <param name="where"></param>
        /// <param name="keys"></param>
        /// <returns></returns>
        public static IDictionary<String, String> ReadWmic(String type, String where, params String[] keys)
        {
            var dic = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);

            var args = $"{type} where {where} get {keys.Join(",")} /format:list";
            var str = Execute("wmic", args)?.Trim();
            if (str.IsNullOrEmpty()) return dic;

            var ss = str.Split(Environment.NewLine);
            foreach (var item in ss)
            {
                var ks = item.Split("=");
                if (ks != null && ks.Length >= 2)
                {
                    var k = ks[0].Trim();
                    var v = ks[1].Trim();
                    if (dic.TryGetValue(k, out var val))
                        dic[k] = val + "," + v;
                    else
                        dic[k] = v;
                }
            }

            // 排序，避免多个磁盘序列号时，顺序变动
            foreach (var item in dic)
            {
                if (item.Value.Contains(','))
                    dic[item.Key] = item.Value.Split(',').OrderBy(e => e).Join();
            }

            return dic;
        }

        private static String Execute(String cmd, String arguments = null)
        {
            try
            {
                var psi = new ProcessStartInfo(cmd, arguments)
                {
                    // UseShellExecute 必须 false，以便于后续重定向输出流
                    UseShellExecute = false,
                    RedirectStandardOutput = true
                };
                var process = Process.Start(psi);
                if (!process.WaitForExit(3_000))
                {
                    process.Kill();
                    return null;
                }

                return process.StandardOutput.ReadToEnd();
            }
            catch { return null; }
        }
        #endregion
    }
}